#ifndef WAYPOINT_H
#define WAYPOINT_H

#include <stdlib.h>
#include <QObject>
#include "../FsxWsUtils/mathhelper.h"
#include "../FsxWsUtils/jsonmarkuphelper.h"
#include "fsxdatadefinitions.h"


namespace FsxSimConnect {

class Aircraft; // forward declaration to avoid cross dependency issue

//! Single Waypoint
/*! \author KWB */
class Waypoint : public QObject
{
    Q_OBJECT

private:
    double _latRad; /*!< latitude in rad */
    double _lngRad; /*!< longitude in rad */
    double _altFt; /*!< longitude in rad */
    double _headingTrueRad; /*!< true heading in rad */
    double _bankRad; /*!< bank angle in rad */
    double _pitchRad; /*!< pitch in rad */
    double _groundspeedKts; /*!< GS kts */
    double _airspeedIndicatedKts; /*!< IAS */
    double _airspeedTrueKts;   /*!< TAS */
    qint64 _timestampUtcEpoch; /*!< UTC epoch timestamp */
    bool _setInstrumentation;  /*!< Instrumentation data are set */
    bool _setPositionAndSpeed; /*!< Position / speed data are set */
    bool _isDelta; /*!< Delta waypoint? */
    bool _onGnd;   /*!< On ground? */
    //! Update the timestamp
    void updateTimestamp();
    //! Aircraft and its waypoints to KML.
    /*! \param level indention level
        \param modelUrl KML model
        \return KML */
    QString toKmlModel(const int level, const QString &modelUrl) const;
    //! Extended KML placemark.
    /*! \param level indention level
        \param modelUrl titel
        \param furtherInfo
        \return KML */
    QString toKmlPlacemarkExtended(const int level, const QString &title = "", const QString &furtherInfo = "") const;
    //! KML placemark with model.
    /*! \param level indention level
        \param modelUrl KML model
        \param title
        \param furtherInfo
        \return KML */
    QString toKmlPlacemarkWithModel(const int level, const QString &modelUrl, const QString &title = "", const QString &furtherInfo = "") const;
public:
    static const double LatLngEpsilon;   /*!< Minimal recognized value */
    static const double AltFtEpsilon;    /*!< Minimal recognized value */
    static const double AngleRadEpsilon; /*!< Minimal recognized value */
    static const double SpeedEpsilon;    /*!< Minimal recognized value */
    //! Default constructor (for metadata(.
    /*! \param parent Qt parent object. */
    explicit Waypoint(QObject *parent=0);
    //! Constructor based on other aircraft, use Aircraft* due to forward declaration
    /*! \param aircraft other aircraft
        \param parent Qt parent object */
    explicit Waypoint(Aircraft* aircraft, QObject *parent=0);
    //! Constructor
    /*! \param lat
        \param lng
        \param altFt
        \param heading
        \param pitch
        \param bank
        \param gs GS
        \param ias IAS
        \param tas TAS
        \param onGnd on ground?
        \param parent Qt parent object */
    explicit Waypoint(double lat, double lng, double altFt, double heading, double pitch, double bank, double gs, double ias, double tas, bool onGnd, QObject *parent = 0);
    //! Constructor based on instrumentation data.
    /*! \param id data
        \param timestamp of this waypoint
        \param parent Qt parent object */
    explicit Waypoint(const FSXAircraftInstrumentationData  &id,  qint64 timestamp=0, QObject *parent=0);
    //! Constructor based on position / speed data.
    /*! \param pas data
        \param timestamp of this waypoint
        \param parent Qt parent object */
    explicit Waypoint(const FSXAircraftPositionAndSpeedData &pas, qint64 timestamp=0, QObject *parent=0);
    //! Delta Waypoint constructor.
    /*! \param newWaypoint latest waypoint
        \param oldWaypoint predecessor waypoint
        \param parent Qt parent object */
    explicit Waypoint(const Waypoint &newWaypoint, const Waypoint &oldWaypoint, QObject *parent=0);
    //! Copy constructor
    /*! \param otherWaypoint to be copied */
    Waypoint(const Waypoint &otherWaypoint);
    //! Assignment operator
    /*! \param otherWaypoint to be assigned */
    Waypoint& operator = (const Waypoint &otherWaypoint);
    //! Compound assignment
    /*! \param otherWaypoint add values */
    Waypoint& operator +=(const Waypoint &otherWaypoint);
    //! Add
    /*! \param otherWaypoint add values */
    Waypoint  operator + (const Waypoint &otherWaypoint);
    //! Compound assignment
    /*! \param otherWaypoint substract values */
    Waypoint& operator -=(const Waypoint &otherWaypoint);
    //! Minus.
    /*! \param otherWaypoint substract values */
    Waypoint  operator - (const Waypoint &otherWaypoint);
    //! Compound assignment.
    /*! \param divisor */
    Waypoint &operator /=(const int divisor);
    //! Divide
    /*! \param divisor */
    Waypoint operator  /(const int divisor);
    //! Delta waypoint?
    /*! \return bool delta waypoint? */
    bool isDelta() const { return _isDelta; }
    //! Aircraft on ground?
    /*! \return on ground? */
    bool onGnd() const { return _onGnd; }
    //! Latitude [rad].
    /*! \return latitude */
    double getLatitudeRad() const { return abs(_latRad) >= LatLngEpsilon ? _latRad : 0.0; }
    //! Longitude [rad].
    /*! \return longitude */
    double getLongitudeRad() const { return abs(_lngRad) >= LatLngEpsilon ? _lngRad : 0.0; }
    //! Altitude ASL [ft]
    /*! \return altitude */
    double getAltitudeFt() const { return abs(_altFt) >= AltFtEpsilon ? _altFt : 0.0; }
    //! Altitude ASL [m]
    /*! \return altitude */
    double getAltitudeM() const { return MathHelper::feetToMeters(Waypoint::getAltitudeFt());}
    //! Latitude [degrees].
    /*! \return latitude */
    double getLatitudeDeg() const { return MathHelper::radianToDegree(getLatitudeRad()); }
    //! Longitude [degrees].
    /*! \return longitude */
    double getLongitudeDeg() const { return MathHelper::radianToDegree(getLongitudeRad()); }
    //! Bank angle in rad.
    /*! \return bank */
    double getBankRad() const { return abs(_bankRad >= AngleRadEpsilon) ? _bankRad : 0.0; }
    //! Bank angle in degrees.
    /*! \return bank */
    double getBankDeg() const { return MathHelper::radianToDegree(getBankRad()); }
    //! Pitch in rad.
    /*! \return pitch */
    double getPitchRad() const { return abs(_pitchRad) >= AngleRadEpsilon ? _pitchRad : 0.0; }
    //! Pitch in degress.
    /*! \return pitch */
    double getPitchDeg() const { return MathHelper::radianToDegree(getPitchRad()); }
    //! Tilt (pitch) in degress.
    /*! \return tilt (pitch) */
    double getTiltDeg() const { return MathHelper::pitchRadToKmlTiltDeg(getPitchRad()); }
    //! IAS kts
    /*! \return ias */
    double getAirspeedIndicatedKts() const { return abs(_airspeedIndicatedKts) >= SpeedEpsilon ? _airspeedIndicatedKts : 0.0; }
    //! TAS kts
    /*! \return tas */
    double getAirspeedTrueKts() const { return abs(_airspeedTrueKts) >= SpeedEpsilon ? _airspeedTrueKts : 0.0; }
    //! GS kts
    /*! \return GS */
    double getGroundspeedKts() const { return abs(_groundspeedKts) >= SpeedEpsilon ? _groundspeedKts : 0.0; }
    //! True heading (degrees).
    /*! \return true heading */
    double getHeadingTrueDeg() const { return MathHelper::radianToDegree(getHeadingTrueRad());}
    //! True heading (rad).
    /*! \return true heading */
    double getHeadingTrueRad() const { return abs(_headingTrueRad) >= AngleRadEpsilon ? _headingTrueRad : 0.0;}
    //! Waypoint's timestamp (UTC epoch ms).
    /*! \return timestamp */
    qint64 getTimestampUtcEpoch() const { return _timestampUtcEpoch; }
    //! Waypoint timestamp within time frame (now-n ms), min. resolution WPTIMEFRAMEMIN
    /*! \param frameMs represents now until now-frameMs
        \return within given time frame */
    bool isWithinTimeFrame(const qint64 frameMs) const;
    //! Waypoint newer (equal) than given timestamp?
    /*! \param timestamp epoch timestamp
        \return newer or equal to timestamp */
    bool isNewerOrEqual(const qint64 timestamp) const { return _timestampUtcEpoch >= timestamp; }
    //! Timestamp as Local time.
    QString getFormattedTimeLocal() const { return _isDelta ? QString::number(_timestampUtcEpoch) : FSXHelper::formattedTime(_timestampUtcEpoch);}
    //! Timestamp as UTC time.
    /*! \return formatted time */
    QString getFormattedTimeUtc() const { return _isDelta ? QString::number(_timestampUtcEpoch) : FSXHelper::formattedTime(_timestampUtcEpoch, false);}
    //! Age (difference to now) in millisecons.
    qint64  getAgeMs() const { return QDateTime::currentMSecsSinceEpoch() - _timestampUtcEpoch; }
    //! Set a new timestamp (only require in special cases, e.g. Unit tests)
    /*! \param tsEpoch ms epoch time */
    void setTimestampUtcEpoch(const qint64 tsEpoch) { _timestampUtcEpoch = tsEpoch; }
    //! Latitude (rad)
    /*! \param lat */
    void setLatitudeRad(const double lat) { _latRad = lat; }
    //! Longitude (rad)
    /*! \param lng */
    void setLongitudeRad(const double lng) {_lngRad = lng; }
    //! Altitude MSL (ft)
    /*! \param alt */
    void setAltitudeFt(const double alt) { _altFt = alt; }
    //! Banking (rad)
    /*! \param bank */
    void setBankRad(const double bank) { _bankRad = bank; }
    //! Banking (degrees)
    /*! \param bank */
    void setBankDeg(const double bank) { _bankRad = MathHelper::degreeToRadian(bank); }
    //! Pitch (rad)
    /*! \param pitch */
    void setPitchRad(const double pitch) { _pitchRad = pitch; }
    //! Pitch (degrees)
    /*! \param pitch */
    void setPitchDeg(const double pitch) { _pitchRad = MathHelper::degreeToRadian(pitch); }
    //! Ground speed [kts].
    /*! \param gs groundspeed */
    void setGroundspeedKts(const double gs) { _groundspeedKts = gs;}
    //! IAS [kts].
    /*! \param ias indicated air speed [kts] */
    void setIndicatedAirspeedKts(const double ias) { _airspeedIndicatedKts = ias;}
    //! TAS [kts].
    /*! \param tas */
    void setTrueAirspeedKts(const double tas) { _airspeedTrueKts = tas;}
    //! True heading [rad].
    /*! \param heading */
    void setHeadingTrueRad(const double heading) { _headingTrueRad = heading;}
    //! True heading [deg].
    /*! \param hdg */
    void setHeadingTrueDeg(const double hdg) { _headingTrueRad = MathHelper::degreeToRadian(hdg); }
    //! Position changed, compared to other waypoint
    /*! \param otherWp waypoint for comparison */
    bool hasPositionChanged(const Waypoint& otherWp);
    //! Flight state changed, compared to other waypoint
    /*! \param otherWp waypoint for comparison */
    bool hasFlightStateChanged(const Waypoint &otherWp);
    //! Speed changed, compared to other waypoint
    /*! \param otherWp waypoint for comparison */
    bool hasSpeedChanged(const Waypoint &otherWp);
    //! Changed, compared to other waypoint
    /*! \param otherWp waypoint for comparison */
    bool hasChanged(const Waypoint &otherWp);
    //! Position / speed data valid?
    /*! \return valid? */
    bool hasValidPositionAndSpeedData() const { return _setPositionAndSpeed; }
    //! Instrumentation data valid?
    /*! \return valid? */
    bool hasValidInstrumentationData() const { return _setInstrumentation; }
    //! Update.
    /*! \param id
        \sa hasValidInstrumentationData */
    void update(const FSXAircraftInstrumentationData &id);
    //! Update.
    /*! \param sd
        \sa hasValidInstrumentationData */
    void update(const FSXAircraftPositionAndSpeedData &sd);
    //! JSON representation of waypoint.
    /*! \param level indention level
        \return JSON string */
    QString toJson(const int level) const;
    //! KML placemarks
    /*! \param level indention level
        \param extPlacemark
        \param modelPlacemark
        \param modelUrl
        \param title
        \param furtherInfo */
    QString toKmlPlacemarks(const int level, const bool extPlacemark, const bool modelPlacemark, const QString &modelUrl, const QString &title, const QString &furtherInfo= "") const;
    //! Look at position (KML) of waypoint.
    /*! \param level indention level */
    QString toKmlLookAt(const int level) const;
};
} // namespace

#endif // WAYPOINT_H
